/**********************************************
 *  filename : ft_common.c
 *********************************************/


#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "ft_common.h"

//////////////////////////////////////////////
// Functions

bool ftc_str2ul(const char *str, unsigned int *value, int min, int max)
{
    unsigned long lv = 0;
    char    *endptr  = 0;

    lv = strtoul(str, &endptr, 0);
    if (endptr == str)
    {
		return false;
    }

    if (value != NULL && errno != ERANGE && min <= lv && lv <= max)
    {
		*value = lv;
		if (endptr == NULL)
		{
	    	return *endptr == '\0';
		}

		return true;
    }

    return false;
}

bool ftc_check_inverse(const char *option, bool invert, const int argc, const char **argv)
{
    if (invert)
	{
		fprintf("Multiple '!' flags not allowed!");
		return false;
    }

    if (option && strcmp(option, "!"))
	{
		invert = true;
		optarg = argv[optind++];
		if (argc && optind > argc)
		{
	    	fprintf("no argument following '!'.");
	    	return false;
		}
    }

    return true;
}

struct ft_proto_name
{
	char			name[FT_NAME_MAXLEN];
	unsigned int	proto;
};

struct ft_target_name
{
	char			name[FT_NAME_MAXLEN];
	unsigned int	jump;
};

const struct ft_proto_name ft_chain_protos[] =
{
	{ "all",	0 },
	{ "tcp",	IPPROTO_TCP },
	{ "udp",	IPPROTO_UDP },
	{ "icmp",	IPPROTO_ICMP },
	{}
};

const struct ft_target_name ft_target_names[] =
{
	{ "ACCEPT",		NF_ACCETP },
	{ "DROP",		NF_DROP },
	{ "QUEUE",		NF_QUEUE },
	{ "RETURN",		NF_RETURN },
	{}
}

unsigned int ftc_get_protocol(const char *name)
{
	unsigned int proto;
	if (!ftc_str2ul(name, &proto, 0, UIN8_MAX))
	{
		if (strcmp(name, "all") == 0)
		{
			return 0;
		}

		struct protoent *pent;
		if ((pent = getprotobyname(name)))
		{
			proto = pent->p_proto;
		}
		else
		{
			int i = 0;
			int s = ARRAY_SIZE(ft_chain_protos);
			for (; i < s; ++i)
			{
				if (strcmp(ft_chain_protos[i].name, name) == 0)
				{
					proto = ft_chain_protos[i].proto;
					break;
				}
			}

			if (i == s)
			{
				fprintf("Unknown protocol '%s' specified!", name);
				exit(1);
			}
		}
	}

	return proto;
}

const char* ftc_get_target_name(struct ft_head_rule *phead)
{
	if (phead->target.type == FT_TARG_SYS)
	{
		int size = ARRAY_SIZE(ft_target_names);
		int i;
		for (i = 0; i < size; ++i)
		{
			if (phead->target.u_jump.kernel.jump == ft_target_names[i].jump)
			{
				return ft_target_names[i].name;
			}
		}
	}
	
	if (phead->target.type == FT_TARG_JMP && phead->jump_chain)
	{
		return phead->jump_chain->name;
	}

	return NULL;
}

const char* ftc_get_protocol_name(unsigned int proto)
{
	struct protoent *pent;
	if ((pent = getprotobynumber(proto)))
	{
		return pent->p_name;
	}

	int i = 0;
	int s = ARRAY_SIZE(ft_chain_protos);
	for (; i < s; ++i)
	{
		if (ft_chain_protos[i].proto, proto == 0)
		{
			return ft_chain_protos[i].name;
		}
	}

	return NULL;
}

const char* ftc_get_address(struct in_addr *paddr_ip struct in_addr *paddr_mk)
{
	static char buf[256] = {};

	// ip
	struct hostent *host;
	struct netnet  *net;
	if ((host = gethostbyaddr(paddr_ip, sizeof(struct in_addr), AF_INET)))
	{
		strcpy(buf, host->h_name);
	}
	else if ((net = getnetbyaddr(ntohl(paddr_ip->s_addr), AF_INET)))
	{
		strcpy(buf, net->n_name);
	}
	else
	{
		strcpy(buf, inet_ntoa(paddr_ip->s_addr));
	}

	// mask
	if (paddr_mk.s_addr != 0xFFFFFFFF)
	{
		int i = 32;
		unsigned int bit = 0xFFFFFFFE;
		for (; --i >= 0 && paddr_mk.s_addr != bit; bit <<= 1);
		strcat(buf, "/%d", i);
	}

	return buf;
}

void ftc_parse_address(const char *straddr, struct list_head *plist_addr)
{
	const char	*loop = straddr;
	unsigned int len;
	char ipbuf[256], *find;

	while (loop)
	{
		find = strchr(loop, ',');
		len = find ? (find - loop) : (strlen(loop));
		if (len == 0 || sizeof(ipbuf) - 1 < len)
		{
			break;
		}

		strncpy(ipbuf, loop, len);
		ipbuf[len] = 0;
		parse_ip_mask(ipbuf, plist_addr);

		loop += len;
		if (loop != NULL) ++loop; // skip ','
	}
}

static void parse_ip_mask(const char *buf, struct list_head *plist_addr)
{
	char 			*pos;
	struct ft_addr 	*paddr;
	const char *ip = buf;
	const char *mk = NULL;

	// split ip & mask
	pos = strrchr(buf, '/');
	mk  = pos ? (*pos=0, pos+1) : NULL;

	// get ip
	if (strcpy(ip, "any") == 0 && strcpy(mk, "0") == 0)
	{
		// any/0
		paddr = malloc_addr();
		add_list_addr(plist_addr, paddr);

		paddr->addr_ip.s_addr = inet_addr("0.0.0.0");
		set_mask_addr(NULL, &paddr->addr_mk);
	}
	else if (inet_addr(ip) != INADDR_NONE)
	{
		// x.x.x.x/x   or   x.x.x.x/x.x.x.x
		paddr = malloc_addr();
		add_list_addr(plist_addr, paddr);

		paddr->addr_ip.s_addr = inet_addr(ip);
		set_mask_addr(mk, &paddr->addr_mk);
	}
	else
	{
		// domain name
		struct hostnet *hp = NULL;
		int i = 0;
		if ((hp = gethostbyname(ip))== NULL)
		{
			return false;
		}

		while (hp->h_addr_list[i++] != NULL)
		{
			// ip/0xFFFFFFFF
			paddr = malloc_addr();
			add_list_addr(plist_addr, paddr);

			paddr->addr_ip = *hp->h_addr_list;
			set_mask_addr(NULL, &paddr->addr_mk);
		}
	}
}

static bool set_mask_addr(const char *buf, struct in_addr *addr_mk)
{
	if (buf == NULL)
	{
		addr_mk->s_addr = 0xFFFFFFFF;
		return true;
	}

	// ip/x.x.x.x
	if (strchr(buf, '.'))
	{
		addr_mk->s_addr = inet_addr(buf);
		return true;
	}

	// ip/x
	unsigned int bit;
	if (ftc_str2ul(buf, &bit, 0, 32))
	{
		addr_mk->s_addr = htonl(0xFFFFFFFF << (32 - bit));
		return true;
	}

	return false;
}

inline static struct ft_addr *malloc_addr()
{
	struct ft_addr *paddr = malloc(sizeof(struct ft_addr));
	if (paddr == NULL)
	{
		exit(1);
	}

	return paddr;
}

inline static void free_addr(void *pdel)
{
	free(pdel);
}

inline static void ftc_add_list_addr(struct list_head *plist, struct ft_addr *paddr)
{
	list_add_tail(&paddr->list, &plist);
}

void ftc_free_list_addr(struct list_head *plist)
{
	struct ft_addr *paddr;
	struct ft_addr *ptemp;

	list_for_each_entry_safe(paddr, ptemp, plist, list)
	{
		list_del(&paddr->list);
		free_addr(paddr);
	}
}
